#include"include.h"
/*
 * Decodes a bencoded string
 */
list_t *bdecode(char *dat, long datlen)
{
   //#warning the last substantial memory leaks occur in here.
   //#warning be wary of mysubstr and createList()
   list_t *l = createList();
   int i;

   //loggerLog(LOG_DEBUG, "Bdecoding string: %s", dat);

   for(i=0;i<datlen;)
   {
      be_dat *d;
      d = bdecodeelement(dat, &i, datlen);

      if(d == NULL) //Already logged in bdecodeel
	 return NULL;

      addObject(l, d);

      loggerLog(LOG_DEBUG, "Parsed element of type %s\n", 
	    (d->type == BE_STR) ? "string" 
	    : (d->type == BE_INT) ? "int" 
	    : (d->type == BE_LIST) ? "list" 
	    : (d->type == BE_DICT) ? "dict" : "UNKNOWN");
   }
   return l;
}

/* Since something like 100% of what we decode is a bencoded dictionary why not 
 * have a specialize method for it
 */
dict_t *bdecodeDict(char *dat, long datlen)
{
   list_t *bdat = bdecode(dat, datlen);
   if(listGetSize(bdat) < 1)
   {
      loggerLog(LOG_ERR, "Could not decode data. Is this valid bencoded data?\n");
      return NULL;
   }
   be_dat *d = getObject(bdat);
   if(d->type != BE_DICT)
   {
      loggerLog(LOG_ERR, "Bencoded data wasn't a bencoded dictionary. Are you running a torrent client or are you drunk again?");
      return NULL;
   }
   
   return d->dat.d;

}


/*
 * Decodes an element at the given index and incremenmts the index over it.
 * Returns the decoded element
 */
be_dat *bdecodeelement(char *dat, int *ndx, long datlen)
{
   be_dat *d = malloc(sizeof(be_dat));
   int i = *ndx;

   loggerLog(LOG_DEBUG, "Bencoded char at %d is %c\n", i, dat[i]);

   if(isInt(dat[i])) //String 3:egg == egg
   {
      loggerLog(LOG_DEBUG, "String length start %d\n", i);

      int len = dat[i] - '0';
      while(isInt(dat[++i]))
      {
	 len *= 10;
	 len += dat[i] - '0';
      }

      loggerLog(LOG_DEBUG, "Length is %d\n", len);

      if(dat[i] != ':')
      {
	 loggerLog(LOG_ERR, "Malformed bencoded data: no ':' after string length decleration at char %d.\n", i);
	 return NULL;
      }
      i++; //Consume ':'
      
      loggerLog(LOG_DEBUG, "String is at %d\n", i);

      d->type = BE_STR;
      mystr *mys = malloc(sizeof(mystr));
      mys->len = (long)len;
      mys->s = mysubstr(dat, i, len);
      d->dat.s = mys;
      i += len;

      loggerLog(LOG_DEBUG,"String over at %d\n", i);

   }
   else if(dat[i] == 'i') //Int i32e == 32 
   {
      loggerLog(LOG_DEBUG, "Int at %d\n", i);

      i++; //consume 'i'
      int isNeg = 0;
      long long inty = 0;
      if(dat[i] == '-')
      {
	 isNeg = 1; i++;
      }
      inty = dat[i] - '0';
      while(isInt(dat[++i]))
      {
	 inty *= 10;
	 inty += dat[i] - '0';
      }
      if(isNeg)
	 inty *= -1;
      if(dat[i] != 'e')
	 return NULL;
      i++; //consume e
      
      loggerLog(LOG_DEBUG,"End of int at %d\n", i);

      d->type = BE_INT;
      d->dat.i = inty;
   }
   else if(dat[i] == 'l') //List l3:eggi32ee == egg,32
   {

      loggerLog(LOG_DEBUG,"List at %d\n", i);

      int listStart = i;

      i++; //consume l

      list_t *l = createList();
      while(dat[i] != 'e' && i < datlen)
      {
	 be_dat *da = bdecodeelement(dat, &i, datlen);
	 if(da == NULL)
	    return NULL;
	 
	 loggerLog(LOG_DEBUG,"Adding to list: "); //printBeDat(da); printf("\n");

	 addObject(l, da);
      }

      if(dat[i] != 'e') // Hit end of string before end of list
      {
	 loggerLog(LOG_ERR, "Malformed bencoded data: unmatched list start at char %d.\n", listStart);
	 return NULL;
      }
      d->type = BE_LIST;
      d->dat.l = l;
      i++;

      loggerLog(LOG_DEBUG,"List ended at %d\n", i);
   }
   else if(dat[i] == 'd') //Dictionary dKeyValuee
   {
      loggerLog(LOG_DEBUG,"Dict at %d\n", i);

      int dictStart = i;
      i++; //Consume d
      dict_t *dic = newDict((int (*)(const void *, const void *))strcmp);
      while(dat[i] != 'e')
      {
	 be_dat *key = bdecodeelement(dat, &i, datlen); 
         //SOMETHING TO LOOK AT, this key has been nonnull, type = BE_STR, but key->dat was null.
         //LOOK INTO IT 
	 if(key == NULL)
	    return NULL;
	 if(key->type != BE_STR)
	 {
	    loggerLog(LOG_ERR, "Malformed bencoded data: dict key was not a string at char %d.\n", i);
	    return NULL;
	 }

	 be_dat *value = bdecodeelement(dat,  &i, datlen);
	 if(value == NULL)
	    return NULL;

         //Technically could contain nullbyte. needs to be mystr. Bad idea
	 loggerLog(LOG_DEBUG,"Adding key %s\n", key->dat.s->s); 

	 dictPut(dic, key->dat.s->s, value);
      }
      i++; //Consume the e
      uint8_t *hash= malloc(20);
      SHA1Context sha;
      int err = SHA1Reset(&sha);
      if(err)
	 loggerLog(LOG_ERR,"SHA1Reset Error %d.\n", err);
      
      err = SHA1Input(&sha,(unsigned char *) mysubstr(dat, dictStart, i - dictStart), i - dictStart);
      if(err)
	 loggerLog(LOG_ERR,"SHA1Input Error %d.\n", err);

      err = SHA1Result(&sha, hash);
      if(err)
	 loggerLog(LOG_ERR,"SHA1Result Error %d.\n", err);

      dic->hash = hash;
      d->type = BE_DICT;
      d->dat.d = dic;
   }
   else
      i++;
   *ndx = i;
   return d;
}


int bestrcmp(be_dat *l, be_dat *r)
{
   if(l->type != r->type)
      return 0;
   if(l->type == BE_STR)
      return strcmp(l->dat.s->s, r->dat.s->s);
   if(l->type == BE_INT)
      return l->dat.i - r->dat.i;
   return 0;
}

/*
 * Prints a piece of bencoded data
 */
void printBeDat(be_dat *d)
{
   if(d->type == BE_STR)
      printf("\"%s\" ", d->dat.s->s);
   else if(d->type == BE_INT)
      printf("%lld ", d->dat.i);
   else if(d->type == BE_LIST)
   {
      printf("( ");
      resetList(d->dat.l);
      if(!endList(d->dat.l))
	 printBeDat((be_dat *)getObject(d->dat.l));
      while(!endList(d->dat.l))
      {
	 printf(", ");
	 printBeDat((be_dat *)getObject(d->dat.l));
      }
      printf(")");
   }
   else if(d->type == BE_DICT)
   {
      list_t *l = dictTraversal(d->dat.d);
      resetList(l);
      printf("[ ");
      if(!endList(l))
      {
	 dict_key_val_t *kv = getObject(l);
	 printf("\"%s\"->", ((char *)kv->key));
	 printBeDat((be_dat *)kv->val);
      }
      while(!endList(l))
      {
	 printf(", ");
	 dict_key_val_t *kv = getObject(l);
	 printf("\"%s\"-> ", ((char *)kv->key));
	 printBeDat((be_dat *)kv->val);
      }
      printf("]");
   }
}

void beFree(list_t *bencoded_data)
{
   for(resetList(bencoded_data); !endList(bencoded_data);)
   {
      be_dat *dat = getObject(bencoded_data);
      beFreeDat(dat);
   }
}

void beFreeDict(dict_t *be_dict)
{
   list_t *tmpList = dictTraversal(be_dict);
   for(resetList(tmpList); !endList(tmpList);)
   {
      dict_key_val_t *kv = getObject(tmpList);
      char *key = kv->key;
      be_dat *value = kv->val;
      free(key);
      beFreeDat(value);
   }
   dictClear(be_dict);
}

void beFreeDat(be_dat *dat)
{
   switch(dat->type)
   {
      case BE_STR:
         mystrfree(dat->dat.s);
         break;
      case BE_INT:
         break;
      case BE_LIST:
         beFree(dat->dat.l);
         break;
      case BE_DICT:
         beFreeDict(dat->dat.d);
         break;
   }
   free(dat);
}
